#include "network.h"
#include "globals.h"
#include "player.h"
#include "npc.h"
#include "GetTime.h"

Network::Network()
{
    server = RakNet::RakPeerInterface::GetInstance();
    server->Startup(MAX_CLIENTS, &RakNet::SocketDescriptor(SERVER_PORT, 0), 1);
    server->SetMaximumIncomingConnections(MAX_CLIENTS);

    output.write("Listening on port %d", SERVER_PORT);
}

Network::~Network()
{

}

void Network::shutdown()
{
    RakNet::RakPeerInterface::DestroyInstance(server);
}

unsigned char Network::GetPacketIdentifier(RakNet::Packet *p)
{
	if (p == 0)
		return 255;

	if ((unsigned char)p->data[0] == ID_TIMESTAMP)
	{
		RakAssert(p->length > sizeof(MessageID) + sizeof(Time));
		return (unsigned char) p->data[sizeof(RakNet::MessageID) + sizeof(RakNet::Time)];
	}
	else
		return (unsigned char) p->data[0];
}

void Network::send_message(const RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, RakNet::SystemAddress address)
{
    server->Send(bitStream, priority, reliability, 0, address, false);
}

void Network::broadcast_message(const RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability)
{
    server->Send(bitStream, priority, reliability, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
}

void Network::receive_data()
{
    for (packet=server->Receive(); packet; server->DeallocatePacket(packet), packet=server->Receive())
    {
        packetIdentifier = GetPacketIdentifier(packet);

        switch (packetIdentifier)
        {
            case ID_NEW_INCOMING_CONNECTION:
                output.write("Connection incoming from %s (%s)", packet->systemAddress.ToString(true), packet->guid.ToString());
                break;

            case ID_NO_FREE_INCOMING_CONNECTIONS:
                output.write("The server is full");
                break;

            case ID_DISCONNECTION_NOTIFICATION:
            {
                Player* player = Player::GetPlayerFromGUID(packet->guid);

                if (player != NULL)
                {
                    RakNet::BitStream bsOut;

                    bsOut.Write((RakNet::MessageID) ID_PLAYER_PART);
                    bsOut.Write((RakNet::MessageID) ID_QUIT_DISCONNECT);
                    bsOut.Write(packet->guid);

                    server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, true);
                    output.write("%s (%s) disconnected!", player->get_name().c_str(), packet->guid.ToString());
                    delete player;
                }

                break;
            }

            case ID_CONNECTION_LOST:
            {
                Player* player = Player::GetPlayerFromGUID(packet->guid);

                if (player != NULL)
                {
                    RakNet::BitStream bsOut;

                    bsOut.Write((RakNet::MessageID) ID_PLAYER_PART);
                    bsOut.Write((RakNet::MessageID) ID_QUIT_CONNECTION_LOST);
                    bsOut.Write(packet->guid);

                    server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, true);
                    output.write("%s (%s) lost connection!", player->get_name().c_str(), packet->guid.ToString());
                    delete player;
                }

                break;
            }

            case ID_ACCOUNT_CREATE:
            {
                RakNet::RakString user;
                RakNet::RakString pass_hash;
                RakNet::BitStream bsIn(packet->data, packet->length, false);

                bsIn.IgnoreBytes(sizeof(RakNet::MessageID));
                bsIn.Read(user);
                bsIn.Read(pass_hash);
                output.write("CREATE USER %s WITH PASSWORD HASH %s", user.C_String(), pass_hash.C_String());

                if (db.account_create(std::string(user), std::string(pass_hash)))
                {
                    output.write("CREATED SUCCESSFULLY");
                    RakNet::BitStream bsOut;
                    bsOut.Write((RakNet::MessageID)ID_ACCOUNT_CREATE_OK);
                    server->Send(&bsOut, MEDIUM_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
                }
                else
                {
                    output.write("ACCOUNT CREATE FAILED");
                    RakNet::BitStream bsOut;
                    bsOut.Write((RakNet::MessageID)ID_ACCOUNT_CREATE_FAILED);
                    server->Send(&bsOut, MEDIUM_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
                }

                break;
            }

            case ID_ACCOUNT_LOGIN:
            {
                RakNet::RakString user;
                RakNet::RakString pass_hash;
                RakNet::BitStream bsIn(packet->data, packet->length, false);

                bsIn.IgnoreBytes(sizeof(RakNet::MessageID));
                bsIn.Read(user);
                bsIn.Read(pass_hash);
                output.write("LOGIN USER %s WITH PASSWORD HASH %s", user.C_String(), pass_hash.C_String());

                if (db.account_login(std::string(user), std::string(pass_hash)))
                {
                    output.write("LOGIN SUCCESSFUL");

                    int id = db.accountNameToID(std::string(user));

                    RakNet::BitStream bsOut;
                    bsOut.Write((RakNet::MessageID)ID_ACCOUNT_LOGIN_OK);
                    bsOut.Write(id);
                    server->Send(&bsOut, MEDIUM_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
                }
                else
                {
                    output.write("LOGIN FAILED");
                    RakNet::BitStream bsOut;
                    bsOut.Write((RakNet::MessageID)ID_ACCOUNT_LOGIN_FAILED);
                    server->Send(&bsOut, MEDIUM_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
                }


                break;
            }

            case ID_CHARACTER_INFO:
            {
                int owner;
                RakNet::BitStream bsIn(packet->data, packet->length, false);

                bsIn.IgnoreBytes(sizeof(RakNet::MessageID));
                bsIn.Read(owner);

                std::vector<character> characters = db.get_characters(owner);

                RakNet::BitStream bsOut;

                for (int i = 0 ; i < characters.size(); i++)
                {
                    bsOut.Write((RakNet::MessageID)ID_CHARACTER_INFO);
                    bsOut.Write(characters[i].id);
                    bsOut.Write(characters[i].name.c_str());
                    bsOut.Write(characters[i].type);
                    bsOut.Write(characters[i].level);
                    server->Send(&bsOut, MEDIUM_PRIORITY, RELIABLE, 0, packet->systemAddress, false);

                    bsOut.Reset();
                }

                break;
            }

            case ID_CHARACTER_CREATE:
            {
                int owner;
                RakNet::RakString name;
                RakNet::BitStream bsIn(packet->data, packet->length, false);

                bsIn.IgnoreBytes(sizeof(RakNet::MessageID));
                bsIn.Read(owner);
                bsIn.Read(name);

                output.write("CREATE CHARACTER %s", name.C_String());

                if (db.character_create(owner, std::string(name)))
                {
                    output.write("CREATED SUCCESSFULLY");
                    RakNet::BitStream bsOut;
                    bsOut.Write((RakNet::MessageID)ID_CHARACTER_CREATE_OK);
                    server->Send(&bsOut, MEDIUM_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
                }
                else
                {
                    output.write("CHARACTER CREATE FAILED");
                    RakNet::BitStream bsOut;
                    bsOut.Write((RakNet::MessageID)ID_CHARACTER_CREATE_FAILED);
                    server->Send(&bsOut, MEDIUM_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
                }

                break;
            }

            case ID_PLAYER_JOIN:
            {
                /* Broadcast the join of player */

                //RakNet::RakString name;
                int id;
                RakNet::BitStream bsIn(packet->data, packet->length, false); // The false is for efficiency so we don't make a copy of the passed data
                bsIn.IgnoreBytes(sizeof(RakNet::MessageID));
                bsIn.Read(id);

                character c = db.get_character(id);

                RakNet::BitStream bsOut;
                bsOut.Write((RakNet::MessageID)ID_PLAYER_JOIN);
                bsOut.Write(c.name.c_str());
                bsOut.Write(packet->guid);

                server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, true);

                /* Send the list of joined players */

                std::map<RakNet::RakNetGUID, Player*> Players = Player::GetPlayerList();
                for (std::map<RakNet::RakNetGUID, Player*>::iterator i = Players.begin(); i != Players.end(); i++)
                {
                    bsOut.Reset();
                    RakNet::RakNetGUID guid = i->first;
                    Player* player = Player::GetPlayerFromGUID(guid);
                    bsOut.Write((RakNet::MessageID)ID_PLAYER_JOIN);
                    bsOut.Write(player->get_name().c_str());
                    bsOut.Write(i->first);

                    server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false);
                }

                /* Send the list of npcs */

                std::map<int, Npc*> Npcs = Npc::GetNpcList();
                for (std::map<int, Npc*>::iterator i = Npcs.begin(); i != Npcs.end(); i++)
                {
                    bsOut.Reset();
                    int id = i->first;
                    Npc* npc = Npc::GetNpcFromID(id);

                    bsOut.Write((RakNet::MessageID)ID_NPC_SPAWN);
                    bsOut.Write(i->first);
                    bsOut.Write(npc->get_name().c_str());
                    bsOut.Write(npc->x());
                    bsOut.Write(npc->y());

                    server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false);

                    if (npc->moving())
                    {
                        bsOut.Reset();
                        bsOut.Write((RakNet::MessageID)ID_NPC_FLAG);
                        bsOut.Write(i->first);
                        bsOut.Write(npc->flag_x());
                        bsOut.Write(npc->flag_y());

                        server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false);
                    }
                }

                Player* newPlayer = new Player(packet->guid, c.name, 64, 64);
                output.write("%s (%s) joined the game!", c.name.c_str(), packet->guid.ToString());

                break;
            }


            case ID_PLAYER_UPDATE:
            {
                /* Get data about the player */

                RakNet::BitStream bsIn(packet->data, packet->length, false);
                bsIn.IgnoreBytes(sizeof(RakNet::MessageID) * 2);

                switch (packet->data[1])
                {
                    case ID_PLAYER_POS:
                    {
                        int x, y;

                        bsIn.Read(x);
                        bsIn.Read(y);

                        RakNet::BitStream bsOut;

                        bsOut.Write((RakNet::MessageID)ID_PLAYER_UPDATE);
                        bsOut.Write((RakNet::MessageID)ID_PLAYER_POS);
                        bsOut.Write(x);
                        bsOut.Write(y);
                        bsOut.Write(packet->guid.ToString());

                        server->Send(&bsOut, HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, true);
                    }
                    break;

                    case ID_PLAYER_HP:
                    {
                        int hp;

                        bsIn.Read(hp);

                        RakNet::BitStream bsOut;

                        bsOut.Write((RakNet::MessageID)ID_PLAYER_UPDATE);
                        bsOut.Write((RakNet::MessageID)ID_PLAYER_HP);
                        bsOut.Write(hp);
                        bsOut.Write(packet->guid.ToString());

                        server->Send(&bsOut, HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, true);
                        break;
                    }


                    case ID_PLAYER_MAXHP:
                    {
                        int maxhp;

                        bsIn.Read(maxhp);

                        RakNet::BitStream bsOut;

                        bsOut.Write((RakNet::MessageID)ID_PLAYER_UPDATE);
                        bsOut.Write((RakNet::MessageID)ID_PLAYER_MAXHP);
                        bsOut.Write(maxhp);
                        bsOut.Write(packet->guid.ToString());

                        server->Send(&bsOut, HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, true);
                        break;
                    }

                    case ID_PLAYER_MP:
                    {
                        int mp;

                        bsIn.Read(mp);

                        RakNet::BitStream bsOut;

                        bsOut.Write((RakNet::MessageID)ID_PLAYER_UPDATE);
                        bsOut.Write((RakNet::MessageID)ID_PLAYER_MP);
                        bsOut.Write(mp);
                        bsOut.Write(packet->guid.ToString());

                        server->Send(&bsOut, HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, true);
                        break;
                    }


                    case ID_PLAYER_MAXMP:
                    {
                        int maxmp;

                        bsIn.Read(maxmp);

                        RakNet::BitStream bsOut;

                        bsOut.Write((RakNet::MessageID)ID_PLAYER_UPDATE);
                        bsOut.Write((RakNet::MessageID)ID_PLAYER_MAXMP);
                        bsOut.Write(maxmp);
                        bsOut.Write(packet->guid.ToString());

                        server->Send(&bsOut, HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, true);
                        break;
                    }

                    default:
                        break;
                }

                break;
            }


            case ID_PLAYER_FLAG:
            {
                int x, y;
                RakNet::MessageID useTimeStamp; // Assign this to ID_TIMESTAMP
                RakNet::Time timeStamp; // Put the system time in here returned by GetTime()
                RakNet::MessageID typeId; // This will be assigned to a type I've added after ID_USER_PACKET_ENUM, lets say ID_SET_TIMED_MINE
                RakNet::BitStream bsIn(packet->data, packet->length, false); // The false is for efficiency so we don't make a copy of the passed data

                bsIn.Read(useTimeStamp);
                bsIn.Read(timeStamp);
                bsIn.Read(typeId);
                //bsIn.IgnoreBytes(sizeof(RakNet::MessageID));

                //output.write("TIMESTAMP: %i", timeStamp);

                bsIn.Read(x);
                bsIn.Read(y);

                Player* player = Player::GetPlayerFromGUID(packet->guid);
                player->create_flag(x, y);

                RakNet::BitStream bsOut;
                //bsOut.Write(useTimeStamp);
                //bsOut.Write(timeStamp);
                //bsOut.Write(typeId);

                bsOut.Write((RakNet::MessageID)ID_TIMESTAMP);
                bsOut.Write(RakNet::GetTime());
                bsOut.Write((RakNet::MessageID)ID_PLAYER_FLAG);

                bsOut.Write(x);
                bsOut.Write(y);
                bsOut.Write(packet->guid);

                server->Send(&bsOut, HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, true);

                break;
            }

            case ID_PLAYER_ATTACK:
            {
                int npcid;
                RakNet::BitStream bsIn(packet->data, packet->length, false);

                bsIn.IgnoreBytes(sizeof(RakNet::MessageID));
                bsIn.Read(npcid);

                Player* player = Player::GetPlayerFromGUID(packet->guid);
                Npc* npc = Npc::GetNpcFromID(npcid);

                player->attack(npc);
                if (npc != NULL)
                {
                    npc->attack(player);
                }

                //if (npcid > 0)
                //{
                    //Npc* npc = Npc::GetNpcFromID(npcid);
                    //npc->attack(packet->guid);
                //}

                break;
            }

            default:
                output.write("Message with identifier %i has arrived", packet->data[0]);
                break;
        }

        //output.write("AVG PING: %i", server->GetAveragePing(packet->systemAddress));
    }
}
